#pragma once
#include "Polynomial.h"

template <typename T> class PolynomialResidue
{
private:
	Polynomial<T> poly;
	Polynomial<T> mod;
public:
	PolynomialResidue() {}
	PolynomialResidue(const T &n): poly(n), mod() {}
	PolynomialResidue(const Polynomial<T> &poly): poly(poly), mod() {}
	PolynomialResidue(const Polynomial<T> &poly, const Polynomial<T> &mod, bool ok = false)
	{
		if (!Degree(mod))
			throw;

		if (ok)
			this->poly = poly;
		else
			this->poly = poly % mod;

		this->mod = mod;
	}
	const Polynomial<T>& Value() const
	{
		return this->poly;
	}
	const Polynomial<T>& Modulus() const
	{
		return this->mod;
	}
	std::string str() const
	{
		return this->poly.str();
	}
	friend std::ostream& operator<<(std::ostream &out, const PolynomialResidue &poly)
	{
		return out << poly.str();
	}
	friend bool IsSameSystem(const PolynomialResidue &lhs, const PolynomialResidue &rhs)
	{
		return lhs.mod == Polynomial<T>() || rhs.mod == Polynomial<T>() || lhs.mod == rhs.mod;
	}
	friend bool operator==(const PolynomialResidue &lhs, const PolynomialResidue &rhs)
	{
		if (lhs.mod == Polynomial<T>() && rhs.mod == Polynomial<T>())
			return lhs.poly == rhs.poly;
		else if (lhs.mod == Polynomial<T>())
			return lhs.poly % rhs.mod == rhs.poly;
		else if (rhs.mod == Polynomial<T>())
			return rhs.poly % lhs.mod == lhs.poly;
		else
			return lhs.poly == rhs.poly && lhs.mod == rhs.mod;
	}
	friend bool operator!=(const PolynomialResidue &lhs, const PolynomialResidue &rhs)
	{
		return !(lhs == rhs);
	}
	PolynomialResidue operator-(const PolynomialResidue &poly) const
	{
		return PolynomialResidue(-poly.poly, poly.mod, true);
	}
	friend PolynomialResidue operator+(const PolynomialResidue &lhs, const PolynomialResidue &rhs)
	{
		if (!IsSameSystem(lhs, rhs))
			throw;

		if (lhs.mod == Polynomial<T>())
		{
			if (rhs.mod == Polynomial<T>())
				return lhs.poly + rhs.poly;
			else
				return PolynomialResidue(lhs.poly + rhs.poly, rhs.mod);
		}
		else
			return PolynomialResidue(lhs.poly + rhs.poly, lhs.mod);
	}
	PolynomialResidue& operator+=(const PolynomialResidue &poly)
	{
		if (!IsSameSystem(*this, poly))
			throw;

		if (this->mod == Polynomial<T>())
		{
			if (poly.mod == Polynomial<T>())
			{
				this->poly += poly.poly;
				return *this;
			}
			else
				return *this = PolynomialResidue(this->poly + poly.poly, poly.mod);
		}
		else
		{
			this->poly = (this->poly + poly.poly) % this->mod;
			return *this;
		}
	}
	friend PolynomialResidue operator-(const PolynomialResidue &lhs, const PolynomialResidue &rhs)
	{
		if (!IsSameSystem(lhs, rhs))
			throw;

		if (lhs.mod == Polynomial<T>())
		{
			if (rhs.mod == Polynomial<T>())
				return lhs.poly - rhs.poly;
			else
				return PolynomialResidue(lhs.poly - rhs.poly, rhs.mod);
		}
		else
			return PolynomialResidue(lhs.poly - rhs.poly, lhs.mod);
	}
	PolynomialResidue& operator-=(const PolynomialResidue &poly)
	{
		if (!IsSameSystem(*this, poly))
			throw;

		if (this->mod == Polynomial<T>())
		{
			if (poly.mod == Polynomial<T>())
			{
				this->poly -= poly.poly;
				return *this;
			}
			else
				return *this = PolynomialResidue(this->poly - poly.poly, poly.mod);
		}
		else
		{
			this->poly = (this->poly - poly.poly) % this->mod;
			return *this;
		}
	}
	friend PolynomialResidue operator*(const PolynomialResidue &lhs, const PolynomialResidue &rhs)
	{
		if (!IsSameSystem(lhs, rhs))
			throw;

		if (lhs.mod == Polynomial<T>())
		{
			if (rhs.mod == Polynomial<T>())
				return lhs.poly * rhs.poly;
			else
				return PolynomialResidue(lhs.poly * rhs.poly, rhs.mod);
		}
		else
			return PolynomialResidue(lhs.poly * rhs.poly, lhs.mod);
	}
	PolynomialResidue& operator*=(const PolynomialResidue &poly)
	{
		if (!IsSameSystem(*this, poly))
			throw;

		if (this->mod == Polynomial<T>())
		{
			if (poly.mod == Polynomial<T>())
			{
				this->poly *= poly.poly;
				return *this;
			}
			else
				return *this = PolynomialResidue(this->poly * poly.poly, poly.mod);
		}
		else
		{
			this->poly = (this->poly * poly.poly) % this->mod;
			return *this;
		}
	}
	friend PolynomialResidue Square(const PolynomialResidue &poly)
	{
		return PolynomialResidue(Square(poly.poly), poly.mod);
	}
};
